package adfc2.util;

import java.io.IOException;
import java.io.Reader;

/**
 * Простой универсальный лексер. Символ полнострочного комментария по умолчанию - #
 */
public final class Lexer {

    public static enum TokenType {
        ID, LONGINT, DOUBLE, SYMBOL, STRING, EOF
    }

    public static final class Token<T> {
        public final TokenType type;
        public final T param;

        public Token(TokenType type, T param) {
            this.type = type;
            this.param = param;
        }

        @Override
        public String toString() {
            return type + "[" + String.valueOf(param) + "]";
        }
    }

    public static final Token<Object> EOF_TOKEN = new Token<Object>(TokenType.EOF, TokenType.EOF);

    private final Reader in;
    private final char comment;
    private int c = ' ';

    public Lexer(Reader in) {
        this.in = in;
        this.comment = '#';
    }

    public Lexer(Reader in, char comment) {
        this.in = in;
        this.comment = comment;
    }

    public Token<?> next() throws IOException {

        while (Character.isWhitespace(c)) {
            c = in.read();
        }
        if (c == comment) {
            while (c != '\n' && c != -1) c = in.read();
            return next();
        }
        if (c == -1) return EOF_TOKEN;

        StringBuilder s = new StringBuilder();
        // ID
        if (Character.isAlphabetic(c) || c == '_') {
            while (Character.isAlphabetic(c) || Character.isDigit(c) || c == '_') {
                s.append((char) c);
                c = in.read();
            }
            return new Token<String>(TokenType.ID, s.toString());
        }
        // LONGINT/DOUBLE
        if (Character.isDigit(c)) {
            boolean hexval = false;
            boolean doubleval = false;
            while (Character.isDigit(c) || c == 'x' || c == '.') {
                s.append((char) c);
                if (c == 'x') hexval = true;
                if (c == '.') doubleval = true;
                c = in.read();
            }
            if (hexval) return new Token<Long>(TokenType.LONGINT, Long.parseLong(s.toString(), 16));
            else if (doubleval) return new Token<Double>(TokenType.DOUBLE, Double.parseDouble(s.toString()));
            else return new Token<Long>(TokenType.LONGINT, Long.parseLong(s.toString()));
        }
        // STRING
        if (c == '\"' || c == '\'') {
            char delimiter = (char) c;
            c = in.read();
            while (c != delimiter && c != -1) {
                if (c == '\\') {
                    c = in.read();
                    switch (c) {
                        case 'n':
                            s.append('\n');
                            break;
                        case 't':
                            s.append('\t');
                            break;
                        case 'b':
                            s.append('\b');
                            break;
                        case 'f':
                            s.append('\f');
                            break;
                        case 'r':
                            s.append('\r');
                            break;
                        case -1:
                            return EOF_TOKEN;
                        default: // \\ \' \" etc
                            s.append((char) c);
                            break;
                    }
                } else {
                    s.append((char) c);
                }
                c = in.read();
            }
            return new Token<String>(TokenType.STRING, s.toString());
        }
        // SYMBOL
        char cc = (char) c;
        c = ' ';
        return new Token<Character>(TokenType.SYMBOL, cc);
    }

}
